/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration.tree.xpath;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.configuration.tree.DefaultConfigurationNode;
import org.apache.commons.configuration.tree.NodeAddData;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
import org.apache.commons.jxpath.ri.model.NodePointerFactory;
import junit.framework.TestCase;
/**
* Test class for XPathExpressionEngine.
*
* @author Oliver Heger
* @version $Id: TestXPathExpressionEngine.java 502705 2007-02-02 19:55:37Z oheger $
*/
public class TestXPathExpressionEngine extends TestCase
{
/** Constant for the test root node. */
static final ConfigurationNode ROOT = new DefaultConfigurationNode(
"testRoot");
/** Constant for the valid test key. */
static final String TEST_KEY = "TESTKEY";
/** The expression engine to be tested. */
XPathExpressionEngine engine;
protected void setUp() throws Exception
{
super.setUp();
engine = new MockJXPathContextExpressionEngine();
}
/**
* Tests the query() method with a normal expression.
*/
public void testQueryExpression()
{
List nodes = engine.query(ROOT, TEST_KEY);
assertEquals("Incorrect number of results", 1, nodes.size());
assertSame("Wrong result node", ROOT, nodes.get(0));
checkSelectCalls(1);
}
/**
* Tests a query that has no results. This should return an empty list.
*/
public void testQueryWithoutResult()
{
List nodes = engine.query(ROOT, "a non existing key");
assertTrue("Result list is not empty", nodes.isEmpty());
checkSelectCalls(1);
}
/**
* Tests a query with an empty key. This should directly return the root
* node without invoking the JXPathContext.
*/
public void testQueryWithEmptyKey()
{
checkEmptyKey("");
}
/**
* Tests a query with a null key. Same as an empty key.
*/
public void testQueryWithNullKey()
{
checkEmptyKey(null);
}
/**
* Helper method for testing undefined keys.
*
* @param key the key
*/
private void checkEmptyKey(String key)
{
List nodes = engine.query(ROOT, key);
assertEquals("Incorrect number of results", 1, nodes.size());
assertSame("Wrong result node", ROOT, nodes.get(0));
checkSelectCalls(0);
}
/**
* Tests if the used JXPathContext is correctly initialized.
*/
public void testCreateContext()
{
JXPathContext ctx = new XPathExpressionEngine().createContext(ROOT,
TEST_KEY);
assertNotNull("Context is null", ctx);
assertTrue("Lenient mode is not set", ctx.isLenient());
assertSame("Incorrect context bean set", ROOT, ctx.getContextBean());
NodePointerFactory[] factories = JXPathContextReferenceImpl
.getNodePointerFactories();
boolean found = false;
for (int i = 0; i < factories.length; i++)
{
if (factories[i] instanceof ConfigurationNodePointerFactory)
{
found = true;
}
}
assertTrue("No configuration pointer factory found", found);
}
/**
* Tests a normal call of nodeKey().
*/
public void testNodeKeyNormal()
{
assertEquals("Wrong node key", "parent/child", engine.nodeKey(
new DefaultConfigurationNode("child"), "parent"));
}
/**
* Tests nodeKey() for an attribute node.
*/
public void testNodeKeyAttribute()
{
ConfigurationNode node = new DefaultConfigurationNode("attr");
node.setAttribute(true);
assertEquals("Wrong attribute key", "node/@attr", engine.nodeKey(node,
"node"));
}
/**
* Tests nodeKey() for the root node.
*/
public void testNodeKeyForRootNode()
{
assertEquals("Wrong key for root node", "", engine.nodeKey(ROOT, null));
assertEquals("Null name not detected", "test", engine.nodeKey(
new DefaultConfigurationNode(), "test"));
}
/**
* Tests node key() for direct children of the root node.
*/
public void testNodeKeyForRootChild()
{
ConfigurationNode node = new DefaultConfigurationNode("child");
assertEquals("Wrong key for root child node", "child", engine.nodeKey(
node, ""));
node.setAttribute(true);
assertEquals("Wrong key for root attribute", "@child", engine.nodeKey(
node, ""));
}
/**
* Tests adding a single child node.
*/
public void testPrepareAddNode()
{
NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + " newNode");
checkAddPath(data, new String[]
{ "newNode" }, false);
checkSelectCalls(1);
}
/**
* Tests adding a new attribute node.
*/
public void testPrepareAddAttribute()
{
NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "\t@newAttr");
checkAddPath(data, new String[]
{ "newAttr" }, true);
checkSelectCalls(1);
}
/**
* Tests adding a complete path.
*/
public void testPrepareAddPath()
{
NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
+ " \t a/full/path/node");
checkAddPath(data, new String[]
{ "a", "full", "path", "node" }, false);
checkSelectCalls(1);
}
/**
* Tests adding a complete path whose final node is an attribute.
*/
public void testPrepareAddAttributePath()
{
NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
+ " a/full/path@attr");
checkAddPath(data, new String[]
{ "a", "full", "path", "attr" }, true);
checkSelectCalls(1);
}
/**
* Tests adding a new node to the root.
*/
public void testPrepareAddRootChild()
{
NodeAddData data = engine.prepareAdd(ROOT, " newNode");
checkAddPath(data, new String[]
{ "newNode" }, false);
checkSelectCalls(0);
}
/**
* Tests adding a new attribute to the root.
*/
public void testPrepareAddRootAttribute()
{
NodeAddData data = engine.prepareAdd(ROOT, " @attr");
checkAddPath(data, new String[]
{ "attr" }, true);
checkSelectCalls(0);
}
/**
* Tests an add operation with a query that does not return a single node.
*/
public void testPrepareAddInvalidParent()
{
try
{
engine.prepareAdd(ROOT, "invalidKey newNode");
fail("Could add to invalid parent!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation where the passed in key has an invalid format: it
* does not contain a whitspace. This will cause an error.
*/
public void testPrepareAddInvalidFormat()
{
try
{
engine.prepareAdd(ROOT, "anInvalidKey");
fail("Could add an invalid key!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an empty path for the new node.
*/
public void testPrepareAddEmptyPath()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " ");
fail("Could add empty path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation where the key is null.
*/
public void testPrepareAddNullKey()
{
try
{
engine.prepareAdd(ROOT, null);
fail("Could add null path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation where the key is null.
*/
public void testPrepareAddEmptyKey()
{
try
{
engine.prepareAdd(ROOT, "");
fail("Could add empty path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an invalid path.
*/
public void testPrepareAddInvalidPath()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " an/invalid//path");
fail("Could add invalid path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an invalid path: the path contains an
* attribute in the middle part.
*/
public void testPrepareAddInvalidAttributePath()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " a/path/with@an/attribute");
fail("Could add invalid attribute path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an invalid path: the path contains an
* attribute after a slash.
*/
public void testPrepareAddInvalidAttributePath2()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " a/path/with/@attribute");
fail("Could add invalid attribute path!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an invalid path that starts with a slash.
*/
public void testPrepareAddInvalidPathWithSlash()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " /a/path/node");
fail("Could add path starting with a slash!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Tests an add operation with an invalid path that contains multiple
* attribute components.
*/
public void testPrepareAddInvalidPathMultipleAttributes()
{
try
{
engine.prepareAdd(ROOT, TEST_KEY + " an@attribute@path");
fail("Could add path with multiple attributes!");
}
catch (IllegalArgumentException iex)
{
// ok
}
}
/**
* Helper method for testing the path nodes in the given add data object.
*
* @param data the data object to check
* @param expected an array with the expected path elements
* @param attr a flag if the new node is an attribute
*/
private void checkAddPath(NodeAddData data, String[] expected, boolean attr)
{
assertSame("Wrong parent node", ROOT, data.getParent());
List path = data.getPathNodes();
assertEquals("Incorrect number of path nodes", expected.length - 1,
path.size());
Iterator it = path.iterator();
for (int idx = 0; idx < expected.length - 1; idx++)
{
assertEquals("Wrong node at position " + idx, expected[idx], it
.next());
}
assertEquals("Wrong name of new node", expected[expected.length - 1],
data.getNewNodeName());
assertEquals("Incorrect attribute flag", attr, data.isAttribute());
}
/**
* Checks if the JXPath context's selectNodes() method was called as often
* as expected.
*
* @param expected the number of expected calls
*/
protected void checkSelectCalls(int expected)
{
MockJXPathContext ctx = ((MockJXPathContextExpressionEngine) engine).getContext();
int calls = (ctx == null) ? 0 : ctx.selectInvocations;
assertEquals("Incorrect number of select calls", expected, calls);
}
/**
* A mock implementation of the JXPathContext class. This implementation
* will overwrite the <code>selectNodes()</code> method that is used by
* <code>XPathExpressionEngine</code> to count the invocations of this
* method.
*/
static class MockJXPathContext extends JXPathContextReferenceImpl
{
int selectInvocations;
public MockJXPathContext(Object bean)
{
super(null, bean);
}
/**
* Dummy implementation of this method. If the passed in string is the
* test key, the root node will be returned in the list. Otherwise the
* return value is <b>null</b>.
*/
public List selectNodes(String xpath)
{
selectInvocations++;
if (TEST_KEY.equals(xpath))
{
List result = new ArrayList(1);
result.add(ROOT);
return result;
}
else
{
return null;
}
}
}
/**
* A special implementation of XPathExpressionEngine that overrides
* createContext() to return a mock context object.
*/
static class MockJXPathContextExpressionEngine extends
XPathExpressionEngine
{
/** Stores the context instance. */
private MockJXPathContext context;
protected JXPathContext createContext(ConfigurationNode root, String key)
{
context = new MockJXPathContext(root);
return context;
}
/**
* Returns the context created by the last newContext() call.
*
* @return the current context
*/
public MockJXPathContext getContext()
{
return context;
}
}
}